#include "datacache.h"
#include <Qt/qmath.h>

DataCache::UpdateParameters::UpdateParameters(const Point & center, double radius, quint32 detalization):
center(center),
angleRadius(radius),
detalization(detalization)
{}

DataCache::DataCache():
lastCenter(0, 0),
lastAngleRadius(0),
lastDetalization(MapElement::NONE),
doStop(true)
{
    CacheSettings settings;
    settings.blockWidth = settings.blockHeight = 0.5;
    settings.policy = DEFAULT;
    cacheSettings.insert(MapElement::WAY, settings);
    cacheSettings.insert(MapElement::BUILDING, settings);
    elementTypes.append(MapElement::WAY);
    elementTypes.append(MapElement::BUILDING);
    elementBlocks.insert(MapElement::WAY, new QHash<int, QHash<int, Block<QSharedPointer<MapElement> > *> *>());
    elementBlocks.insert(MapElement::BUILDING, new QHash<int, QHash<int, Block<QSharedPointer<MapElement> > *> *>());
    radiusBufRatio = 1.2;
}

double DataCache::bufRadius(double radius)
{
    return qMax(radius * radiusBufRatio, 180.0);
}

void DataCache::stop()
{
    QWriteLocker wLocker(&rwLock);
    doStop = true;
}

void DataCache::update(const Point & center, double angleRadius, quint32 detalization)
{
    QWriteLocker wUpdateLocker(&rwUpdateLock);
    double d = Utils::shortestDistance(lastCenter, center);
    if(/*detalization - (detalization & lastDetalization) > 0
        || */Utils::toDegrees(2.0 * qAsin(d / (2.0 * Utils::Radius))) + angleRadius > lastAngleRadius)
    {
        qDebug("loading data");
        lastCenter = center;
        lastAngleRadius = bufRadius(angleRadius);
        lastDetalization = detalization;
        updateParameters.enqueue(UpdateParameters(lastCenter, lastAngleRadius, lastDetalization));
        start();
    }
}

void DataCache::run()
{
//    qDebug("update started");
    {
        QWriteLocker wLocker(&rwLock);
        doStop = false;
    }
    forever
    {
        QWriteLocker wLocker(&rwLock);
        if(doStop)
            break;
        UpdateParameters params;
        {
            QWriteLocker wUpdateLocker(&rwUpdateLock);
            if(updateParameters.empty())
                break;
            else
                while(updateParameters.size() > 1)
                    updateParameters.erase(updateParameters.begin(), updateParameters.end() - 1);
            params = updateParameters.dequeue();
        }
        for(QLinkedList<MapElement::Type>::const_iterator it = elementTypes.begin(); it != elementTypes.end(); it++)
        {
            if(params.detalization & *it)
            {
                cleanup(*it, params);
                load(*it, params);
            }
        }
    }
//    qDebug("update finished");
}

void DataCache::cleanup(MapElement::Type type, const UpdateParameters & params) {
    int count = 0;
    QHash<int, QHash<int, Block<QSharedPointer<MapElement> > *> *> * vblocks = elementBlocks.value(type);
    for(QHash<int, QHash<int, Block<QSharedPointer<MapElement> > *> *>::iterator vit = vblocks->begin();
        vit != vblocks->end(); )
    {
        QHash<int, Block<QSharedPointer<MapElement> > *> * hblocks = vit.value();
        for(QHash<int, Block<QSharedPointer<MapElement> > *>::iterator it = hblocks->begin(); it != hblocks->end(); )
        {
            if(!Utils::intersects(*(it.value()), params.center, params.angleRadius))
            {
                delete it.value();
                it = hblocks->erase(it);
            } else
            {
                it++;
                count++;
            }
        }
        if(hblocks->empty())
        {
            delete hblocks;
            vit = vblocks->erase(vit);
        } else
        {
            vit++;
        }
    }
}

void DataCache::load(MapElement::Type type, const UpdateParameters & params) {
    int count = 0;
    QHash<int, QHash<int, Block<QSharedPointer<MapElement> > *> *> * vblocks = elementBlocks.value(type);
    CacheSettings settings = cacheSettings.value(type);
    double centerLatitude = ((int)(params.center.getLatitude() / settings.blockHeight)) * settings.blockHeight;
    double centerLongitude = ((int)(params.center.getLongitude() / settings.blockWidth)) * settings.blockWidth;
    Segment segment(Point2d(0, 0), settings.blockHeight, settings.blockWidth);
    QHash<int, Block<QSharedPointer<MapElement> > *> * hblocks = NULL;
    for(double vdir = -1.0; vdir <= 1.0; vdir += 2.0)
    {
        for(double lat = centerLatitude - settings.blockHeight * Utils::sign(1 - vdir);
            qAbs(lat + settings.blockHeight / 2.0) <= 90.0 - settings.blockHeight / 2.0;
            lat += settings.blockHeight * vdir)
        {
//                    qDebug("\t\tlat = %f", lat);
            hblocks = NULL;
            QHash<int, QHash<int, Block<QSharedPointer<MapElement> > *> *>::iterator hblocksIt = vblocks->find(lat * 10000000);
            if(hblocksIt != vblocks->end())
                hblocks = hblocksIt.value();
            count = 0;
            for(double hdir = -1.0; hdir <= 1.0; hdir += 2.0)
            {
                for(double lon = centerLongitude - settings.blockWidth * Utils::sign(1 - hdir);
                    qAbs(lon + settings.blockWidth / 2.0) <= 180.0 - settings.blockWidth / 2.0;
                    lon += settings.blockWidth * hdir)
                {
//                            qDebug("\t\t%f, %f", lat, lon);
                    segment.setBottomLeft(lat, lon);
                    if(!Utils::intersects(segment, params.center, params.angleRadius))
                        break;
                    if(hblocks == NULL)
                        hblocks = new QHash<int, Block<QSharedPointer<MapElement> > *>();

                    QHash<int, Block<QSharedPointer<MapElement> > *>::iterator blockIt = hblocks->find(lon * 10000000);
                    if(blockIt == hblocks->end())
                        hblocks->insert((int)(lon * 10000000.0), loadBlock(type, lat, lon));
                    count++;
                }
            }
            if(count > 0)
                vblocks->insert((int)(lat * 10000000.0), hblocks);
            else
                break;
        }
    }
}

Block<QSharedPointer<MapElement> > * DataCache::loadBlock(MapElement::Type type, double latitude, double longitude)
{
    CacheSettings settings = cacheSettings.value(type);
    return new Block<QSharedPointer<MapElement> >(Point2d(latitude, longitude), settings.blockHeight, settings.blockWidth);
}
